/*
 * #%L
 * Server
 * %%
 * Copyright (C) 2011 Ractoc.com
 * %%
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * #L%
 */
package com.ractoc.opengamefinder.server.plugins;

import java.io.IOException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import org.apache.log4j.Logger;

import com.jme3.network.ConnectionListener;
import com.jme3.network.HostedConnection;
import com.jme3.network.MessageListener;
import com.jme3.network.Network;
import com.jme3.network.Server;
import com.jme3.network.Message;
import com.ractoc.opengamefinder.api.results.MessageResultsErrorCodes;
import com.ractoc.opengamefinder.api.messages.NetworkMessage;
import com.ractoc.opengamefinder.api.network.ClientServerMessage;
import com.ractoc.opengamefinder.api.plugins.BaseSpiderMonkeyPlugin;
import com.ractoc.opengamefinder.api.results.LoginMessageResult;
import com.ractoc.opengamefinder.api.results.NetworkMessageResult;
import com.ractoc.opengamefinder.api.translators.TranslatorMap;
import com.ractoc.opengamefinder.server.plugins.messages.CloseSessionMessage;
import com.ractoc.opengamefinder.server.plugins.messages.ResetSessionTimeoutMessage;
import com.ractoc.opengamefinder.server.plugins.messages.ServerMessage;
import com.ractoc.opengamefinder.server.plugins.messages.SessionTimeoutMessage;
import com.ractoc.opengamefinder.server.plugins.messages.StartSessionMessage;
import com.ractoc.pffj.api.BasePluginMessage;
import com.ractoc.pffj.api.BasePluginMessageResult;
import com.ractoc.pffj.api.PluginLoaderException;
import com.ractoc.pffj.api.PluginMessage;
import com.ractoc.pffj.api.PluginMessageResultListener;
import com.ractoc.pffj.core.PluginController;
import com.ractoc.pffj.core.PluginException;

/**
 * Network plugin using spidermonkey which is currently built into jme3.
 * 
 * @author ractoc
 * @version 0.1
 */
public class SpiderMonkeyPlugin extends BaseSpiderMonkeyPlugin implements ConnectionListener,
        MessageListener<HostedConnection> {

    private static final String SESSION_PLUGIN = "sessionPlugin";

    private static Logger logger = Logger.getLogger(SpiderMonkeyPlugin.class.getName());

    private static final Map<Integer, Integer> playersSessions = new ConcurrentHashMap<Integer, Integer>();

    private int serverTcpPort;
    private int serverUdpPort;

    /**
     * Default constructor.
     */
    public SpiderMonkeyPlugin() {
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public final void load() throws PluginLoaderException {
        // Here we setup the needed connections.
        try {
            registerClasses();
            
            logger.debug("starting server on " + serverTcpPort + ":" + serverUdpPort);
                      
            server =  Network.createServer(serverTcpPort, serverUdpPort);
            server.addConnectionListener(this);
            server.addMessageListener(this);
            server.start();
        }
        catch (ClassNotFoundException ex) {
            logger.error("unable to serialize all classes", ex);
        } catch (final IOException e) {
            logger.error("unable to startup the server.", e);
        }

    }

    /**
     * {@inheritDoc}
     */
    @Override
    public final void processMessage(final PluginMessage msg, final PluginMessageResultListener pmrListener) {
        // Here we process the messages to send to the server.
        BasePluginMessageResult result = null;
        if (msg instanceof NetworkMessage) {
            final NetworkMessage nMsg = (NetworkMessage) msg;
            // here we send the payload to the client...
            final BasePluginMessage pMsg = nMsg.getPayload();
            final NetworkMessageResult nResult = new NetworkMessageResult();
            nResult.setErrorCode(MessageResultsErrorCodes.SUCCESS);
            try {
                int clientId = nMsg.getSessionId();
                if (clientId == 0) {
                    final Integer playerId = nMsg.getPlayerId();
                    if (playerId == 0) {
                        throw new IllegalArgumentException("No sessionId and no playerId provided.");
                    } else {
                        clientId = playersSessions.get(playerId);
                    }
                }
                logger.debug("clientId = " + clientId);
                final HostedConnection hostedConnection = server.getConnection(clientId);

                sendMessage(pMsg, nMsg.getOriginPlugin(), nMsg.getDestinationPlugin(), nMsg.isReliable(), nMsg
                        .isSecure(), hostedConnection);
            } catch (final IOException e) {
                nResult.setErrorCode(MessageResultsErrorCodes.INTERNAL_ERROR);
            }
            result = nResult;
        } else if (msg instanceof SessionTimeoutMessage) {
            final SessionTimeoutMessage sMsg = (SessionTimeoutMessage) msg;
            final int clientId = sMsg.getSessionId();
            logger.debug("disconnecting client " + sMsg.getSessionId());
            final HostedConnection hostedConnection = server.getConnection(clientId);
            hostedConnection.close("Session timed out.");
        }
        if (result != null) {
            pmrListener.receivedResult(result);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public final void connectionAdded(final Server serverParam, final HostedConnection hostedConnection) {
        final StartSessionMessage msg = new StartSessionMessage();
        msg.setSessionId(hostedConnection.getId());
        try {
            PluginController.getCurrentPluginController().processMessage(SESSION_PLUGIN, msg);
        } catch (final PluginException e) {
            logger.error("Unable to create session for clientID " + hostedConnection.getId(), e);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public final void connectionRemoved(final com.jme3.network.Server serverParam,
            final HostedConnection hostedConnection) {
        final CloseSessionMessage msg = new CloseSessionMessage();
        msg.setSessionId(hostedConnection.getId());
        try {
            PluginController.getCurrentPluginController().processMessage(SESSION_PLUGIN, msg);
        } catch (final PluginException e) {
            logger.error("Unable to close session for clientID " + hostedConnection.getId(), e);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public final void messageReceived(final HostedConnection hostedConnection, final Message message) {
        logger.debug("message received: " + message.getClass().getName());
        if (message instanceof ClientServerMessage) {
            final Integer sessionId = hostedConnection.getId();
            try {
                // each time a message is received from the client, we reset the
                // timeout on the session belonging to that client.
                final ResetSessionTimeoutMessage rstMsg = new ResetSessionTimeoutMessage();
                rstMsg.setSessionId(sessionId);
                PluginController.getCurrentPluginController().processMessage(SESSION_PLUGIN, rstMsg);
                final String destinationPlugin = ((ClientServerMessage) message).getDestinationPlugin();
                logger.debug("destinationPlugin=" + destinationPlugin);
                final BasePluginMessage msg = (BasePluginMessage) TranslatorMap.translate(message);
                final ServerMessage sMsg = new ServerMessage();
                sMsg.setSessionId(sessionId);
                sMsg.setPayload(msg);
                final BasePluginMessageResult result = PluginController.getCurrentPluginController().processMessage(
                        destinationPlugin, sMsg);
                if (result != null && result.getErrorCode() != null) {
                    // for performance reasons, the link between player id and
                    // session id is also stored directly in the network plugin.
                    if (result instanceof LoginMessageResult && result.getErrorCode() == MessageResultsErrorCodes.SUCCESS) {
                        if (((LoginMessageResult) result).getAccountVO() != null) {
                            final Integer playerId = ((LoginMessageResult) result).getAccountVO().getId();
                            logger.debug("linking player id " + playerId + " to sessionId " + sessionId);
                            playersSessions.put(playerId, sessionId);
                        }
                    }
                    logger.debug("result error code: " + result.getErrorCode());
                    sendMessage((BasePluginMessage) result, null, ((ClientServerMessage) message).getOriginPlugin(),
                            message.isReliable(), false, hostedConnection);
                } else {
                    logger.info("no result received from the plugincontroller.");
                }
            } catch (final PluginException e) {
                logger.error("Error sending plugin message.", e);
            } catch (final IOException e) {
                logger.error("error sending network message.", e);
            }
        }
    }

    public void setServerTcpPort(int serverTcpPort) {
        this.serverTcpPort = serverTcpPort;
    }

    public int getServerTcpPort() {
        return serverTcpPort;
    }

    public void setServerUdpPort(int serverUdpPort) {
        this.serverUdpPort = serverUdpPort;
    }

    public int getServerUdpPort() {
        return serverUdpPort;
    }

}
